package localSearch;


import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;

import random.TestClusterBuilder;


import instrumentation.CoverageBranchTarget;
import instrumentation.TrackerCoverage;
import GA.*;
import util.Filter;
import Memetic.ReflectionExecutor;
import Operations.CreateObject;
import Operations.Invoke;
import Operations.Operation;
import Operations.ValuesGenerator;
import ReadWriteFieldsRelations.MethodRelations;
import ReadWriteFieldsRelations.ReadWriteMethods;

public class HillClimbing extends LocalSearchPopulation{
	private  TrackerCoverage tracker;
	private  long startTime;
	private  long elapsed;
	private  long TestTimeout;
	@Override	
	public Chromosome execute(){
		startTime=gaInfo.getStartTime();
		TestTimeout=gaInfo.getTestTimeOut();
		elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
		
		tracker = TrackerCoverage.getTracker();
		int trgt=getTargetToCover(tracker);
		
		if( trgt!= -1 ){
			if(trgt==46) //return null ;
				System.out.println("Target to be cobvered: "+trgt );
			Chromosome chrom =tracker.branchNotCoveredByIndividual.get(trgt);
//			System.out.println("Original chrom: "+chrom.toString() );


			Chromosome shortChrom = evalParts(chrom,trgt);
			if(shortChrom==null)
				shortChrom=chrom.clone();

	//		System.out.println("shortChrom : "+shortChrom.toString() );

			Chromosome result = hillClimb(shortChrom,trgt);
			if(result!=null)
				return result;
		}

		return null;
	}
	public Chromosome evalParts(Chromosome chrom,int trgt){
		return ReflectionExecutor.reverseExecut(chrom,trgt);
		
	}
	public int getTargetToCover(TrackerCoverage tracker){
		int trgtBR=-1,BR;
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		List<Integer> keys = new ArrayList<Integer>(trackers.keySet());
		do {
			CoverageBranchTarget tempCondTarget=null;
			for ( BR = 0; BR < trackers.size(); BR++) {
				 tempCondTarget = (CoverageBranchTarget) trackers
						.get(keys.get(BR));
				if(tempCondTarget==null) continue;

				if(tempCondTarget.getfName_1().equals("$assertionsDisabled") || tempCondTarget.getfName_2().equals("$assertionsDisabled"))continue; 
	
				int attempt=tempCondTarget.getAttempt();

				if( tempCondTarget.getQuality() == Float.NEGATIVE_INFINITY) continue;
				if(tempCondTarget.getQuality() ==Float.POSITIVE_INFINITY && trgtBR==-1 &&(attempt!=gaInfo.getAttempt()))
						trgtBR=keys.get(BR);
				
				if (tempCondTarget.getQuality() !=Float.POSITIVE_INFINITY &&(attempt!=gaInfo.getAttempt())){
						trgtBR= keys.get(BR);
						if(tempCondTarget.getfName_1()=="" && tempCondTarget.getfName_2()=="")continue;
						break;
				}
			}


			if(trgtBR==-1)
				gaInfo.setAttempt(gaInfo.getAttempt()+1);
			else{
				tempCondTarget = (CoverageBranchTarget) trackers.get(trgtBR);
				tempCondTarget.setAttempt(gaInfo.getAttempt());
			}

		}
		while(trgtBR==-1 && BR!=trackers.size());

		return trgtBR;
	
	}
	private static final int ITERATIONS = 1000;
	/** Number of attempts when a method is selected */
	private final int TTL_FIRST = 10;
	/** Number of attempts for operations able to reduce the branch distance */
	private final int TTL_IMPROVEMENT  = 50;
	public Chromosome hillClimb (Chromosome individual,int BR){
		int ttl = 0;//how many times that a position/ method can be targeted again
		Chromosome originalIndividual=individual.clone();
		Chromosome newIndividual=null;
		ReflectionExecutor.execute(individual);
	//	float oldFitness = tracker.calculateFitness(individual); 
		
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
		 if(condTarget==null) return null;
		 float oldFitness=condTarget.getQuality();
		ArrayList<Chromosome> l = new ArrayList<Chromosome>(Arrays.asList(tracker.branchCoveredByIndividual));
		 l.removeAll(Collections.singleton(null)); 		 
//		 System.out.println("BR COVERAGE ( " + l.size()  +" ): ");
		 
		 Map<Class<?>, List<String>> recommendMethods=calculateMethodsRecommend(BR);
			Map<Class<?>, List<String>> origRecommendMethods = new LinkedHashMap<Class<?>, List<String>>();
			origRecommendMethods.putAll(recommendMethods);
			
		for (int i = 0; i < ITERATIONS && elapsed < TestTimeout; i++) {

//			System.out.println("Original individual:\t"+ originalIndividual.toString());
//			System.out.println("b4 individual:"+ individual.toString());
//			System.out.println("--------------------------");
			if(--ttl < 0) {
				recommendMethods.putAll(origRecommendMethods);
				List<Class<?>> keys = new ArrayList<Class<?>>(recommendMethods.keySet());
				int cutRand = ValuesGenerator.getRandomInt(recommendMethods.size());
				Class<?> cName = (Class<?>) keys.get(cutRand);
				List<String> recMethodList=	recommendMethods.get(cName);
				int methodRand = 0;//ValuesGenerator.getRandomInt(recMethodList.size());
				List<String> temp=new LinkedList<String>();
				temp.add(recMethodList.get(methodRand));
				recommendMethods.clear();
				recommendMethods.put(cName, temp);
				
				ttl = TTL_FIRST;
			}

			boolean canContinue = mutate(individual,recommendMethods,BR);
//			if(BR==87){
//				System.out.println("Original individual:"+ originalIndividual.toString());
//				System.out.println("af individual:"+ individual.toString()+"\n");
//			}
			
			ReflectionExecutor.execute(individual);
			tracker.calculateFitness(individual); 
			
			ArrayList<Chromosome>  l2 = new ArrayList<Chromosome>(Arrays.asList(tracker.branchCoveredByIndividual));
			l2.removeAll(Collections.singleton(null)); 		 
			 condTarget = (CoverageBranchTarget) trackers.get(BR);
			 if(condTarget==null) return null;
			if(l2.size() >l.size() ){
				 System.out.println("** New BR COVERAGE ( " + l2.size()  +" ): ");
				 l = new ArrayList<Chromosome>(Arrays.asList(tracker.branchCoveredByIndividual));
				 l.removeAll(Collections.singleton(null)); 	
				 newIndividual=individual.clone();
				 if(condTarget.getQuality()==Float.NEGATIVE_INFINITY)
					 	return newIndividual;
			}


				 float newFitness=condTarget.getQuality();
				 if(newFitness>=oldFitness)
					individual=originalIndividual.clone();
				 else{
					 originalIndividual=individual.clone();
					 oldFitness=newFitness;
					 ttl = TTL_IMPROVEMENT;
				 }
				 elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
		}
		return newIndividual;
		
	}
	public Map<Class<?>, List<String>> calculateMethodsRecommend(int BR) {

		List<String> methodsList = new LinkedList<String>();
		Map<Class<?>, List<String>> methodsRecommend = new LinkedHashMap<Class<?>, List<String>>();
		Map<String, List<String>> writeMethods=new LinkedHashMap<String, List<String>>();
		String mName = null;
		Class<?> cName = getClassName(BR);
		String tField = detectField(BR);

		if (tField == null) {
			MethodRelations relations = gaInfo.getMethodRelations();
			mName = getMethod(BR);

				 writeMethods = relations.getRelatedMethods(cName,mName);

			if(writeMethods==null ){
				//return null;
				// call all write methods
				 Map<Class<?>,  List<String>> allWriteMethods = relations.getAllWriteMethods();
				 return allWriteMethods;
			}
			methodsList = writeMethods.get(cName.getName());
			methodsRecommend.put(cName, methodsList);
			System.out.print("");
	//		Map<String, List<String>> fieldsMethods = writeFieldMethods.get(cName);
	//		methodsList = fieldsMethods.get(tField);

		} else {
			if (tField.equals("parameter") || tField.equals(""))
				mName = getMethod(BR);
			if (mName != null)
				methodsList.add(mName);
			else {
				MethodRelations relations = gaInfo.getMethodRelations();
				Map<Class<?>, Map<String, List<String>>> writeFieldMethods = relations
						.buildWriteFiledMethodDependence();
				Map<String, List<String>> fieldsMethods = writeFieldMethods
						.get(cName);
				if(fieldsMethods!=null)
					methodsList = fieldsMethods.get(tField);
				//if the tField is a constant in String calls functions
//				if (methodsList==null){
//					mName = getMethod(BR);
//					methodsList = new LinkedList<String>();
//					methodsList.add(mName);
//				}
			}
			// either filed is not known or the target is in a private method
			if (methodsList == null||methodsList.size()==0) {
				mName = getPrivateMethod(BR);
				if (mName != null) {
					MethodRelations relations = gaInfo.getMethodRelations();
					Map<Class<?>, Map<String, List<String>>> writeFieldMethods = relations
							.buildMethodsCallPrivate(mName);
					Map<String, List<String>> fieldsMethods = writeFieldMethods
							.get(cName);
					methodsList = fieldsMethods.get(mName);
				} else {
					if (methodsList==null||methodsList.size()==0){
					mName = getMethod(BR);
					methodsList = new LinkedList<String>();
					methodsList.add(mName);
				}

				}

			}

			methodsRecommend.put(cName, methodsList);
		}
		return methodsRecommend;
	}
	/** probability to add an operation before the selected operation */
	private float probAdd = 0.05f;

	/** probability to remove the selected operation */
	private float probRemove = 0.15f;

	/** if an AssignPrimitive is selected, this is the probability to modify the value */
	private float probModify = 0.8f;

	public boolean mutate(Chromosome individual,Map<Class<?>, List<String>> methodsRecommend,int BR){
		List<Operation> ops = individual.getTest();
	
		final boolean isModifiable = ops.size() >= 0 ;//&& (methodsRecommend.containsKey("init"));

		final boolean isRemovable = ops.size()>3;

		int choice;
		/** 0 => add; 1 => modify; 2 => remove */
		if(isModifiable && isRemovable) {
			float c = ValuesGenerator.getRandomFloat() * (probAdd + probModify + probRemove);
			if(c < probAdd) choice = 0;
			else if(c < probAdd + probModify) choice = 1;
			else choice = 2;
		} else if(isModifiable) {
			float c = ValuesGenerator.getRandomFloat() * (probAdd + probModify);
			if(c < probAdd) choice = 0;
			else choice = 1;
		} else if(isRemovable) {
			float c = ValuesGenerator.getRandomFloat()* (probAdd + probRemove);
			if(c < probAdd) choice = 0;
			else choice = 2;
		} else choice = 0;

		if(choice==0){
//			System.out.println("add operation");

			int randPos=ValuesGenerator.getRandomIntBetween(0,ops.size());
			int num = ValuesGenerator.getRandomInt(10);
			for (int i = 0; i < num; i++) {
				ops = individual.getTest();
				List<Operation> rep = new LinkedList<Operation>();
				rep.addAll(ops.subList(0, randPos));
				if (methodsRecommend == null)
					rep.add(Operation.randomlyGenerate(gaInfo.getTestCluster()));
				else{
					Operation newOp=Operation.recommendMethodsGenerateOperation(gaInfo.getTestCluster(), methodsRecommend);
					if(newOp!=null)
						rep.add(newOp);
					else
						continue;
				}

				rep.addAll(ops.subList(randPos, ops.size()));
				individual.setTest(rep);
			}
			
		}else if(choice==1){
//			System.out.println("modify operation");
			modify(individual,methodsRecommend,BR);
		}else if(choice==2){
//			System.out.println("remove operation");
			if(ops.size()<=3) return true;
			int randPos=ValuesGenerator.getRandomInt(ops.size());
			ops.remove(randPos);
			individual.setTest(ops);
		}else{
			System.out.println("Error:undefined operation");
		}
		return false;
	}
	private String detectField(int BR){	
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
		if(condTarget.getfName_1().contains("param")||condTarget.getfName_2().contains("param"))
				return "parameter";
		else
			if(condTarget.getfName_1().length()!=0)
				return condTarget.getfName_1();
			else 
				if(condTarget.getfName_2().length()!=0)
					return condTarget.getfName_2();
				else
					if(condTarget.getArgsField().length()!=0)
						return condTarget.getArgsField();
		return null;

	
	}
	private String getMethod(int BR){
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
			if(condTarget.getisMNamePublic())
				return condTarget.getmName();
		
		return null;
	}
	private String getPrivateMethod(int BR){
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
			if(!condTarget.getisMNamePublic())
				return condTarget.getmName();
		
		return null;
	}

	private Class<?> getClassName(int BR){
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
		
		return condTarget.getcName();
		
	}

	private void modify(Chromosome individual,Map<Class<?>, List<String>> recommendedMethods,int BR) {
		//choose a method
		List<Class<?>> keys = new ArrayList<Class<?>>(recommendedMethods.keySet());
		int cutRand = ValuesGenerator.getRandomInt(recommendedMethods.size());
		Class<?> cName = (Class<?>) keys.get(cutRand);
		List<String> methodsRecommendedList=recommendedMethods.get(cName);
		int methodRand = ValuesGenerator.getRandomInt(methodsRecommendedList.size());
		String mName=(String) methodsRecommendedList.get(methodRand);
		//the function that change the value is the construction of a class
		if(mName.equals("<init>")) modifyConstructoParam(individual,cName.getName(),BR);
		List<Operation> ops =individual.getTest();
		
		for(int i=ops.size()-1;i>=0;i--)
			if(ops.get(i) instanceof Invoke){
				Invoke op=(Invoke) ops.get(i);
				String suggestedValue= calculatePredicateValue(BR);
				
				String suggestedType= Filter.PrimitiveGenericConverter(calculatePredicateType(BR));

				final MethodDeclared method =  op.getMethod();
				if(mName!=null)
					if(!method.getName().equals(mName)) continue;
				final Class<?>[] paramsTypes = method.getParameterTypes();
				String[] p = new String[paramsTypes.length];
				String[] preParams=op.getParams();
				String[] params = new String[paramsTypes.length];
				params=op.getParams();

				boolean commitedChange=false;
				for(int k= 0; k < p.length; k++) {
					Class<?> clz=paramsTypes[k];
					do {
					if(Filter.isPrimitiveOrStringType(clz)){
						String paramType=Filter.PrimitiveGenericConverter(paramsTypes[k].getName().toString());
						if(paramType.equals(suggestedType)){
							suggestedValue= ValuesGenerator.modifyValue(paramsTypes[k].getName(),suggestedValue);
							if(! suggestedType.equals("java.lang.String")&&suggestedValue==null) 
								suggestedValue=params[k];
							else{
						//	if(ValuesGenerator.modifyValue(paramsType[k].getName(),suggestedValue))
									params[k] =ValuesGenerator.modifyValue(paramsTypes[k].getName(),suggestedValue);
									suggestedValue=params[k];
							}
//							else{
//								params[k] =ValuesGenerator.buildValue(paramsType[k].getName());
//								suggestedValue=params[k];
//								}

						}
						else{
								if(ValuesGenerator.getRandomInt(100)<50)
									params[k] =ValuesGenerator.buildValue(paramsTypes[k].getName());
									//suggestedValue=params[k];
							}
					}
					else{
						p[k] = Operation.generateRef(method,clz, individual.getReferenceFactory(),individual.getCluster());

						commitedChange=true;
					}
					} while (p[k]==preParams[k]);
				}

				if(commitedChange)
					op.setParams(p);
			}
	}
	private void modifyConstructoParam(Chromosome individual,String cName,int BR){
		TestClusterBuilder TestCluster=gaInfo.getTestCluster();
		Map<String, String[]> refFactory = TestCluster.getRefFactory();
		Map<String, List<CreateObject>> constructorz=individual.getConstructors();
		Map<String, Class<?>> cluster = individual.getCluster();
// get information about predicate
	//	String suggestedField= detectField(BR);
		String suggestedValue= calculatePredicateValue(BR);
		
		String suggestedType= Filter.PrimitiveGenericConverter(calculatePredicateType(BR));
		
		List<CreateObject> listObject=constructorz.get(cName);
		constructorz.clear();
	//	ConstructorDeclared[] cutConst = classToTest.get(cName);
		if (listObject != null) {
			for(CreateObject obj : listObject){
				ConstructorDeclared cut= (ConstructorDeclared) obj.getConstructor().clone();
				Class<?>[] paramsType = cut.getParameterTypes();
				Object[] actualParams = new Object[cut.getParameterTypes().length];
				String[] params = new String[paramsType.length];
				params=cut.getParams();
				String actuallParam;
				for (int k = 0; k < actualParams.length; k++) {
				if (Filter.isPrimitiveOrStringType(cluster.get(paramsType[k].getName().toString()))){
					String paramType=Filter.PrimitiveGenericConverter(paramsType[k].getName().toString());
					if(paramType.equals(suggestedType)){
						actuallParam=params[k];
						if(params[k]==null)
							actuallParam="null";
						if(!actuallParam.equals(suggestedValue)){
						suggestedValue= ValuesGenerator.modifyValue(paramsType[k].getName(),suggestedValue);
						if(! suggestedType.equals("java.lang.String")&&suggestedValue==null) 
							suggestedValue=params[k];
						else{
					//	if(ValuesGenerator.modifyValue(paramsType[k].getName(),suggestedValue))
								params[k] =ValuesGenerator.modifyValue(paramsType[k].getName(),suggestedValue);
								suggestedValue=params[k];
						}
						}
						else{
							if(ValuesGenerator.getRandomInt(100)<50)
								params[k] =ValuesGenerator.buildValue(paramsType[k].getName());
							}

					}
//					else{
//							if(ValuesGenerator.getRandomInt(100)<50)
//								params[k] =ValuesGenerator.buildValue(paramsType[k].getName());
//								//suggestedValue=params[k];
//						}
				}
				else
					params[k] = TestCluster.getReference(paramsType[k].getName(), refFactory);
			}
				cut.setParams(params);
				if (constructorz.get(cName) == null)
					constructorz.put(cName, new LinkedList<CreateObject>());
				List<CreateObject> constr = (List<CreateObject>) constructorz.get(cName);
				constr.add(new CreateObject(cut));
			}
		}
		
	}
	private String calculatePredicateValue(int BR){
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
		if(!condTarget.getfName_1().equals("")||!condTarget.getfName_2().equals("")){
			if(!condTarget.getcValue_1().equals(""))
				return condTarget.getcValue_1();
			else
			if(!condTarget.getcValue_2().equals(""))
				return condTarget.getcValue_2();
		}
			else
				if(!condTarget.getArgsField().equals(""))
					return condTarget.getArgsField();
		
		return null;
	}
	private String calculatePredicateType(int BR){
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
		
				return condTarget.getmClase();
	//			if(!condTarget.getmClase().equals("")&&!condTarget.getmCall().equals(""))
	//	if(!condTarget.getmCall().startsWith("java.lang."))
	//	return null;
	}

}
